package resml

import (
	"bytes"
	"embed"
	"fmt"
	"html/template"
	"io"
	"io/fs"
	"net/http"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

// 嵌入 webui 文件系统。
// 使用方法：
//
// http.Handle("/prefix/", http.StripPrefix("/prefix/", http.FileServerFS(webui)))
//
// 注：go:embed 与它前面的 // 之间不能有空格，否则不会嵌入文件。
//
//go:embed all:webui
var webui embed.FS

// WebUI 组合 fs.FS 与 http.Handler 接口。
// http.FileSystem 接口与 fs.FS 兼容，可以直接转为 http.FileSystem。
type WebUi interface {
	fs.FS
	http.Handler
}

// Web UI 的变量
type WebUiVars struct {
	Title string // 标题
}

// WebUI 文件系统。
type webUiHandler struct {
	mu     sync.Mutex
	prefix string
	index  *TemplateFile
	vars   *WebUiVars
}

// 实现 http.FileSystem 接口。
func (fs *webUiHandler) Open(name string) (fs.File, error) {
	fs.mu.Lock()
	defer fs.mu.Unlock()
	// 此处的 /webui 是嵌入文件系统的前缀，是固定值，而不是请求 path 的前缀
	name = strings.TrimPrefix(name, "/webui/")
	if !strings.HasPrefix(name, "webui/") {
		name = fmt.Sprintf("webui/%s", strings.TrimLeft(name, "/"))
	}
	if strings.HasSuffix(name, "/.") {
		name = name[:len(name)-1] + "index.html"
	}
	fd, err := webui.Open(name)
	if err != nil {
		return nil, err
	}
	if stat, err := fd.Stat(); err == nil && stat.IsDir() {
		if fs.index != nil {
			return fs.index.newReader(), nil
		}
		name = fmt.Sprintf("%s/index.html", strings.TrimRight(name, "/"))
		fd, err = webui.Open(name)
		if err != nil {
			return nil, err
		}
	}
	if strings.HasSuffix(name, "/index.html") {
		if fs.index != nil {
			return fs.index.newReader(), nil
		}
		fs.index, err = NewTemplateFile(fd, name, fs.vars)
		if err != nil {
			return nil, err
		}
		return fs.index.newReader(), nil
	}
	return fd, nil
}

// 实现 http.Handler 接口。
func (fs *webUiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if fs.prefix == "" {
		http.ServeFileFS(w, r, fs, r.URL.Path)
		return
	}
	name := strings.Trim(strings.TrimPrefix(r.URL.Path, fs.prefix), "/") // 去掉前缀与 / 字符
	if name == "" || name == "." {
		name = "index.html"
	}
	http.ServeFileFS(w, r, fs, name)
}

// 创建 WebUi 实例。
func NewWebUi(prefix string) WebUi {
	return NewWebUiVars(prefix, nil)
}

// 创建 WebUi 实例。
func NewWebUiVars(prefix string, vars *WebUiVars) WebUi {
	h := &webUiHandler{prefix: strings.TrimSpace(prefix), vars: vars}
	if h.vars == nil {
		h.vars = &WebUiVars{Title: "文件管理系统"}
	} else if strings.TrimSpace(h.vars.Title) == "" {
		h.vars.Title = "文件管理系统"
	}
	return h
}

// 获取 WebUI 的嵌入文件系统。它的文件路径为 webui/*，例如 webui/index.html。
func WebUiEmbedFs() embed.FS {
	return webui
}

type TemplateFile struct {
	rd   *bytes.Reader
	ln   int64
	buf  []byte
	name string
	mt   time.Time
}

func (f *TemplateFile) newReader() *TemplateFile {
	return &TemplateFile{rd: bytes.NewReader(f.buf), ln: int64(len(f.buf)), name: f.name, mt: f.mt}
}

func (f *TemplateFile) Close() error {
	return nil
}

func (f *TemplateFile) Read(p []byte) (n int, err error) {
	if f.rd != nil {
		return f.rd.Read(p)
	}
	return 0, io.ErrNoProgress
}

func (f *TemplateFile) Stat() (fs.FileInfo, error) {
	return f, nil
}

func (f *TemplateFile) Name() string {
	return f.name
}

func (f *TemplateFile) Size() int64 {
	return f.ln
}

func (f *TemplateFile) Mode() fs.FileMode {
	return 0644
}

func (f *TemplateFile) ModTime() time.Time {
	return f.mt
}

func (f *TemplateFile) IsDir() bool {
	return false
}

func (f *TemplateFile) Sys() any {
	return nil
}

func NewTemplateFile(fd fs.File, name string, vars any) (*TemplateFile, error) {
	body, err := io.ReadAll(fd)
	fd.Close()
	if err != nil {
		return nil, err
	}
	t, err := template.New(name).Parse(string(body))
	if err != nil {
		return nil, err
	}
	buf := new(bytes.Buffer)
	if err = t.Execute(buf, vars); err != nil {
		return nil, err
	}
	return &TemplateFile{buf: buf.Bytes(), name: filepath.Base(name), mt: time.Now()}, nil
}
